package com.tengfei.main.user.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.tengfei.common.exception.ServiceException;
import com.tengfei.common.utils.redis.RedisUtils;
import com.tengfei.main.exam.domain.PaperQu;
import com.tengfei.main.exam.domain.PaperUser;
import com.tengfei.main.exam.domain.PaperUserQu;
import com.tengfei.main.exam.mapper.PaperUserMapper;
import com.tengfei.main.exam.mapper.PaperUserQuMapper;
import com.tengfei.main.question.domain.vo.ObjectiveVo;
import com.tengfei.main.question.domain.vo.SubjectiveVo;
import com.tengfei.main.question.mapper.ObjectiveMapper;
import com.tengfei.main.question.mapper.SubjectiveMapper;
import com.tengfei.main.user.domain.vo.exam.UserPaperObjVo;
import com.tengfei.main.user.domain.vo.exam.UserPaperSubVo;
import com.tengfei.main.user.domain.vo.exam.UserQuVo;
import com.tengfei.main.user.utils.SimilarityUtil;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;

/**
 * @author feige
 * @version v1.0
 * @date 2022-09-14-11:32
 * @description
 */

@Component
public class UserAsyncService {

    @Resource
    private ObjectiveMapper objectiveMapper;
    @Resource
    private SubjectiveMapper subjectiveMapper;
    @Resource
    private PaperUserQuMapper userQuMapper;
    @Resource
    private PaperUserMapper paperUserMapper;

    @Async
    public void initExamQuInfoList(String redisKey, List<PaperQu> paperQus, long duration) {
        paperQus.forEach(item -> {
            // 查询每道题详细信息
            if (isObj(item.getQuType())) {
                ObjectiveVo objectiveVo = objectiveMapper.selectVoById(item.getQuId());
                UserPaperObjVo userPaperObjVo = BeanUtil.copyProperties(objectiveVo, UserPaperObjVo.class);
                userPaperObjVo.setQuScore(item.getQuScore());
                RedisUtils.setCacheObject(redisKey + objectiveVo.getObjId(), userPaperObjVo, Duration.ofSeconds(duration));
            } else {
                SubjectiveVo subjectiveVo = subjectiveMapper.selectVoById(item.getQuId());
                UserPaperSubVo userPaperSubVo = BeanUtil.copyProperties(subjectiveVo, UserPaperSubVo.class);
                userPaperSubVo.setQuScore(item.getQuScore());
                RedisUtils.setCacheObject(redisKey + subjectiveVo.getSubId(), userPaperSubVo, Duration.ofSeconds(duration));
            }
        });
    }

    @Async
    public void saveQuAndCalculate(List<Long> quIds, PaperUser paperUser, String listPrefixKey, String infoPrefixKey) {
        List<PaperUserQu> list = new ArrayList<>();
        int totalScore = 0;
        for (Long quId : quIds) {
            UserQuVo userQu = RedisUtils.getCacheObject(listPrefixKey + quId);
            // 存入数据库
            PaperUserQu paperUserQu = BeanUtil.copyProperties(userQu, PaperUserQu.class);
            paperUserQu.setUserId(paperUser.getUserId());
            paperUserQu.setPaperId(paperUser.getPaperId());
            // 客观题打分，主观题相似性判断打分
            // 用户答案
            String userAnswer = paperUserQu.getUserAnswer();
            int score = 0;
            if (ObjectUtil.isNull(userAnswer)) {
                paperUserQu.setUserScore(score);
            } else {
                switch (paperUserQu.getQuType()) {
                    case 1:
                    case 2:
                        score = singleQuScore(userAnswer, infoPrefixKey, quId);
                        break;
                    case 3:
                        score = multipleQuScore(userAnswer, infoPrefixKey, quId);
                        break;
                    case 4:
                        score = completionScore(userAnswer, infoPrefixKey, quId);
                        break;
                    case 5:
                        score = shortQuScore(userAnswer, infoPrefixKey, quId);
                        break;
                }
            }
            paperUserQu.setUserScore(score);
            totalScore += score;
            list.add(paperUserQu);
        }
        // 插入用户考试题目记录
        userQuMapper.insertBatch(list);
        // 更改用户成绩信息
        paperUserMapper.update(null, new LambdaUpdateWrapper<PaperUser>()
                .set(PaperUser::getUserPaperScore, totalScore)
                .eq(PaperUser::getPaperId, paperUser.getPaperId())
                .eq(PaperUser::getUserId, paperUser.getUserId()));

    }

    // 单选题分数（判断题）
    private Integer singleQuScore(String userAnswer, String infoPrefixKey, Long quId) {
        UserPaperObjVo objQu = RedisUtils.getCacheObject(infoPrefixKey + quId);
        String quAnswer = objQu.getObjAnswer();
        if (userAnswer.equals(quAnswer)) {
            return objQu.getQuScore();
        }
        return 0;
    }

    // 多选题
    private Integer multipleQuScore(String answer, String infoPrefixKey, Long quId) {
        UserPaperObjVo objQu = RedisUtils.getCacheObject(infoPrefixKey + quId);
        String[] quAnswers = objQu.getObjAnswer().split("\\$");
        String[] userAnswers = answer.split("\\$");
        int quAnsLen = quAnswers.length, userAnsLen = userAnswers.length;
        if (userAnsLen <= 0) {
            return 0;
        }
        int matchAns = 0;
        for (String quAnswer : quAnswers) {
            for (String userAnswer : userAnswers) {
                if (quAnswer.equals(userAnswer)) {
                    matchAns++;
                    break;
                }
            }
        }
        int score = 0;
        if (quAnsLen == userAnsLen) {
            if (matchAns == quAnsLen) {
                score = objQu.getQuScore();
            }
        } else if (userAnsLen < quAnsLen) {
            if (matchAns == userAnsLen) {
                score = objQu.getQuScore() / 2 + 1;
            }
        }
        return score;
    }

    // 填空题
    private Integer completionScore(String answer, String infoPrefixKey, Long quId) {
        UserPaperSubVo subQu = RedisUtils.getCacheObject(infoPrefixKey + quId);
        String[] quAnswers = subQu.getSubAnswer().split("\\$");
        String[] userAnswers = answer.split("\\$");
        int quAnsLen = quAnswers.length, userAnsLen = userAnswers.length;
        // 相似比例
        double similarRate = 0;
        for (int i = 0; i < userAnsLen; i++) {
            if (userAnswers[i].equals(quAnswers[i])) {
                similarRate += 1;
            } else {
                Double rate = SimilarityUtil.getCosineSimilarity(quAnswers[i], userAnswers[i]);
                similarRate += rate;
            }
        }
        double doubleScore = similarRate * subQu.getQuScore() / quAnsLen;
        // 界限判断（四舍五入）
        Integer score = Convert.toInt(doubleScore, 0);
        if (doubleScore - score > 0.5) {
            return score + 1;
        } else {
            return score;
        }
    }

    // 简答题
    private Integer shortQuScore(String answer, String infoPrefixKey, Long quId) {
        UserPaperSubVo subQu = RedisUtils.getCacheObject(infoPrefixKey + quId);
        String[] quAnswers = subQu.getSubAnswer().split("\\$");
        String[] userAnswers = answer.split("\\$");
        int quAnsLen = quAnswers.length, userAnsLen = userAnswers.length;
        // 相似比例
        double similarRate = 0;
        for (int i = 0; i < userAnsLen; i++) {
            Double rate = SimilarityUtil.getCosineSimilarity(quAnswers[i], userAnswers[i]);
            similarRate += rate;
        }
        double doubleScore = similarRate * subQu.getQuScore() / quAnsLen;
        // 界限判断（四舍五入）
        Integer score = Convert.toInt(doubleScore, 0);
        if (doubleScore - score > 0.5) {
            return score + 1;
        } else {
            return score;
        }
    }

    // 非空判断+界限判断+是否为客观题
    private boolean isObj(Integer quType) {
        if (quType.intValue() < 1 || quType.intValue() > 5) {
            throw new ServiceException("题目类型不符合规范");
        } else if (quType.intValue() >= 1 && quType.intValue() <= 3) {
            return true;
        } else {
            return false;
        }
    }


}
